﻿using Platformer_Example.Controller.Collision;
using Platformer_Example.Controller.Interfaces;
using Platformer_Example.Controller.Map;
using Platformer_Example.Controller.Terrain;
using System;
using Transformable_Engine_v2.Engine;
using Transformable_Engine_v2.Engine.GameObjects.Containers;
using Transformable_Engine_v2.Engine.Transformable_Math.Geometry;

namespace Platformer_Example.Controller.Character
{
    public abstract class BaseGravityAffected : Container, ICollidable
    {
        private const float MAX_GRAVITY = 128;
        private const float PUSHBACK_NORMAL_DECAY = 0.25f;
        private const float PUSHBACK_FRICTION_DECAY = 5;
        private const float MIN_PUSHBACK_LENGTH = 1;
        private static readonly TE_Vector2 Gravity = new TE_Vector2(0, 4);

        public TE_Vector2 GravityVector { get; protected set; }
        protected TE_Vector2 MovementVector { get; private set; }
        public TE_Vector2 PushbackVector { get; private set; }

        protected abstract bool IsObject { get; }

        protected BaseGravityAffected()
        {
            GravityVector = new TE_Vector2();
            MovementVector = new TE_Vector2();
            PushbackVector = new TE_Vector2();
        }

        public Map.Map CurrentMap { get; private set; }

        public virtual void ChangeMap(string newMapId, CharacterPosition newState)
        {
            var newMap = Map.Map.MapDictionary[newMapId];

            if (CurrentMap != null)
                CurrentMap.Leave(this, IsObject);
            
            Move(newState.Position);

            CurrentMap = newMap;
            newMap.Enter(this, IsObject);
        }

        public abstract void Move(TE_Vector2 position);

        public virtual void ChangeMap(string newMapId, TE_Vector2 newPosition)
        {
            var newMap = Map.Map.MapDictionary[newMapId];

            if (CurrentMap != null)
                CurrentMap.Leave(this, IsObject);

            Move(newPosition);

            CurrentMap = newMap;
            newMap.Enter(this, IsObject);
        }

        protected abstract TE_Rectangle CollisionWarp { get; }
        protected TE_Rectangle GetWarpCollisionArea()
        {
            return CollisionWarp + AbsolutePosition;
        }
        public void ApplyWarps()
        {
            MapPlayerData newPosition = CurrentMap.CheckWarps(GetWarpCollisionArea());

            if (newPosition != null)
            {
                ChangeMap(newPosition.MapName, newPosition.NewPosition);
            }
        }

        public abstract TE_Rectangle GetCollisionBox();

        public TE_Rectangle CollisionArea
        {
            get { return AbsolutePosition + GetCollisionBox(); }
        }

        public void ApplyGravity(TimeSpan gameTime, float percentageAffected = 1)
        {
            if (percentageAffected > 0)
            {
                GravityVector += Gravity * gameTime.TotalSeconds * percentageAffected * Game.Options.GridSize;

                if (GravityVector.Length > MAX_GRAVITY)
                    GravityVector = new TE_Vector2(GravityVector.Angle, MAX_GRAVITY);
            }
        }

        public void SetPushback(TE_Vector2 pushback)
        {
            PushbackVector = pushback;
        }

        public void SetMovement(TE_Vector2 movement)
        {
            MovementVector = movement;
        }

        void ApplyPushbackX(TimeSpan gameTime, bool addFriction, float percentageAffected = 1)
        {
            if (percentageAffected > 0)
            {
                PushbackVector.X *= (float)(1 - (addFriction ? PUSHBACK_FRICTION_DECAY : PUSHBACK_NORMAL_DECAY) * gameTime.TotalSeconds * percentageAffected);

                if (Math.Abs(PushbackVector.X) <= MIN_PUSHBACK_LENGTH)
                {
                    ResetPushbackX();
                }
            }
        }

        void ApplyPushbackY(TimeSpan gameTime, bool addFriction, float percentageAffected = 1)
        {
            if (percentageAffected > 0)
            {
                PushbackVector.Y *= (float)(1 - (addFriction ? PUSHBACK_FRICTION_DECAY : PUSHBACK_NORMAL_DECAY) * gameTime.TotalSeconds * percentageAffected);

                if (Math.Abs(PushbackVector.Y) <= MIN_PUSHBACK_LENGTH)
                {
                    ResetPushbackY();
                }
            }
        }

        public void ResetMovement()
        {
            MovementVector = new TE_Vector2();
        }

        public void ResetGravityY()
        {
            GravityVector.Y = 0;
        }

        public void ResetGravityX()
        {
            GravityVector.X = 0;
        }

        public BaseTerrain.TerrainType ApplyMovementX(TimeSpan gameTime)
        {
            var resultingOffset = (float)(gameTime.TotalSeconds * Game.Options.GridSize * (GravityVector.X + MovementVector.X + PushbackVector.X));
            if (Math.Abs(resultingOffset) < 0.0001f)
                return BaseTerrain.TerrainType.None;

            BaseTerrain.TerrainType collisionType = CollisionDetection.ResolveCollisionX(CurrentMap, this, resultingOffset);

            if (collisionType == BaseTerrain.TerrainType.Left)
            {
                ResetGravityX();
                ResetPushbackX();
                ApplyPushbackY(gameTime, true);
            }
            else if (collisionType == BaseTerrain.TerrainType.Right)
            {
                ResetGravityX();
                ResetPushbackX();
                ApplyPushbackY(gameTime, true);
            }
            else
            {
                AddToAbsolutePosition(new TE_Vector2(resultingOffset, 0));
                ApplyPushbackY(gameTime, false);
            }

            return collisionType;
        }

        private void ResetPushbackX()
        {
            PushbackVector.X = 0;
        }
        private void ResetPushbackY()
        {
            PushbackVector.Y = 0;
        }

        public BaseTerrain.TerrainType ApplyMovementY(TimeSpan gameTime, bool applyFreeFall)
        {
            var resultingOffset = (float)(gameTime.TotalSeconds * Game.Options.GridSize * (GravityVector.Y + MovementVector.Y + PushbackVector.Y));
            if (Math.Abs(resultingOffset) < 0.0001f)
                return BaseTerrain.TerrainType.None;

            BaseTerrain.TerrainType collisionType = CollisionDetection.ResolveCollisionY(CurrentMap, this, resultingOffset);
            if (collisionType == BaseTerrain.TerrainType.Floor)
            {
                //ResetGravityY();
                ResetPushbackY();
                ApplyPushbackX(gameTime, true);
            }
            else if (collisionType == BaseTerrain.TerrainType.Ceiling)
            {
                ResetGravityY();
                ResetPushbackY();
                ApplyPushbackX(gameTime, true);
            }
            else
            {
                if (applyFreeFall)
                {
                    AddToAbsolutePosition(new TE_Vector2(0, resultingOffset));
                    ApplyPushbackX(gameTime, false);
                }
            }

            return collisionType;
        }

        public abstract void AddToAbsolutePosition(TE_Vector2 offset);

        public abstract void MoveAbsolute(TE_Vector2 newPosition);

        public abstract TE_Vector2 GetAbsolutePosition();
    }
}